/*
 * Copyright (c) 2012 Sean Connelly
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

var common = require('./common.js');

// The basic idea behind dyn-tree.js is that it creates a tree, with the ability to generate nodes
// on-demand (based on regular expression nodes).  It also allows jQuery-like chaining.

function dynTree_createRaw(obj, root)
{
	return {
			_root: root,
			_tree: obj,
			_addChild: function(name, data) {
					if (typeof data !== 'object' || data === null)
						throw 'invalid data';
					this._tree.children[name] = dynTree_createRaw({
							name: name,
							parent: this,
							path: [].concat(this._tree.path, [name]),
							children: {},
							regexps: [],
							data: data
						}, this._root);
					return this;
				},
			add: function(name, data) { // add a named node
					if (this.exists(name))
						throw name + ' already exists';
					if (typeof data == 'undefined')
						data = {};
					return this._addChild(name, data);
				},
			// generators will execute when attempting to navigate to a node that matches the pattern,
			// which will call the `createFunc` function in order to get the data for the node
			addGenerator: function(regex, createFunc, afterFunc) {
					if (typeof regex == 'string')
						regex = new RegExp(regex);
					this._tree.regexps.push({
							regex: regex,
							create: createFunc,
							done: afterFunc
						});
					return this;
				},
			to: function(name) { // navigate to a child (and execute generator if needed)
					if (typeof this._tree.children[name] !== 'undefined')
						return this._tree.children[name];
					for (var i = 0; i < this._tree.regexps.length; i++)
					{
						var o = this._tree.regexps[i];
						var m = name.match(o.regex);
						if (m !== null)
						{
							var obj = this._addChild(name, o.create(m));
							if (typeof o.done == 'function')
								o.done.apply(this, [obj._tree.children[name]]);
							return obj._tree.children[name];
						}
					}
					throw name + ' not found';
				},
			toAdd: function(name, data) { // add and navigate at once
					return this.add(name, data).to(name);
				},
			up: function() { // move up a level
					if (this._tree.parent === false)
						throw 'already at top level';
					return this._tree.parent;
				},
			toPath: function(path) { // jump to an absolute path
					var m = this._root;
					for (var i = 0; i < path.length; i++)
						m = m.to(path[i]);
					return m;
				},
			root: function() { // jump to root
					return this._root;
				},
			exists: function(name) { // test if `name` exists (or if a generator would create `name`)
					if (typeof this._tree.children[name] !== 'undefined')
						return true;
					for (var i = 0; i < this._tree.regexps.length; i++)
					{
						var m = name.match(this._tree.regexps[i].regex);
						if (m !== null)
							return true;
					}
					return false;
				},
			path: function() { // return the current path ['one', 'two'], etc
					return this._tree.path.concat(); // get copy of path
				},
			getData: function() { // get the data at this node
					return this._tree.data;
				},
			setData: function(data) { // set the data at this node
					this._tree.data = data;
					return this;
				},
			getName: function() { // get the name of the current node
					return this._tree.name;
				},
			updateData: function(upd) { // update the data at this node
					for (var k in upd)
						this._tree.data[k] = upd[k];
					return this;
				},
			dump: function() {
					var outtab = function(obj, t) {
							var out = '';
							var tab = common.stringRepeat('  ', t);
							for (var k in obj._tree.children)
							{
								out += tab + k + ': ' +
									JSON.stringify(obj._tree.children[k].getData()) + '\n' +
									outtab(obj.to(k), t + 1);
							}
							for (var i = 0; i < obj._tree.regexps.length; i++)
								out += tab + obj._tree.regexps[i].regex.toString() + '\n';
							return out.replace(/\n+$/g, '');
						};
					return outtab(this, 0);
				}
		};
}

function dynTree_create()
{
	var r = dynTree_createRaw({
			name: '',
			parent: false,
			path: [],
			children: {},
			regexps: [],
			data: {}
		}, false);
	r._root = r;
	return r;
}

exports.create = dynTree_create;
